package com.sfdcj.se.meta.soap;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.rpc.ServiceException;

import com.sfdcj.se.SfdcException;
import com.sforce.soap._2006._04.metadata.AsyncResult;
import com.sforce.soap._2006._04.metadata.Metadata;
import com.sforce.soap._2006._04.metadata.MetadataBindingStub;
import com.sforce.soap._2006._04.metadata.MetadataServiceLocator;
import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.SessionHeader;
import com.sforce.soap.partner.SforceServiceLocator;
import com.sforce.soap.partner.SoapBindingStub;
import com.sforce.soap.partner.fault.ExceptionCode;
import com.sforce.soap.partner.fault.LoginFault;

public class MetadataService {
	// binding for the Enterprise WSDL used for login() call
	private SoapBindingStub binding;
	// binding for the metadata WSDL used for create() and checkStatus() calls
	private MetadataBindingStub metadatabinding;

	static BufferedReader rdr = new BufferedReader(new InputStreamReader(
			System.in));

	// one second in milliseconds
	private static final long ONE_SECOND = 1000;

	public MetadataService() {
	}

	public void login(String userName, String password) {

		try {
			binding = (SoapBindingStub) new SforceServiceLocator().getSoap();
		} catch (ServiceException e) {
			throw new SfdcException(e, "Binding failure!!");
		}

		// Time out after a minute
		binding.setTimeout(60000);
		// Log in using the Enterprise WSDL binding
		LoginResult loginResult;
		try {
			loginResult = binding.login(userName, password);
		} catch (LoginFault ex) {
			String message;
			// The LoginFault derives from AxisFault
			ExceptionCode exCode = ex.getExceptionCode();
			if (exCode == ExceptionCode.FUNCTIONALITY_NOT_ENABLED
					|| exCode == ExceptionCode.INVALID_CLIENT
					|| exCode == ExceptionCode.INVALID_LOGIN
					|| exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_DOMAIN
					|| exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_TIME
					|| exCode == ExceptionCode.ORG_LOCKED
					|| exCode == ExceptionCode.PASSWORD_LOCKOUT
					|| exCode == ExceptionCode.SERVER_UNAVAILABLE
					|| exCode == ExceptionCode.TRIAL_EXPIRED
					|| exCode == ExceptionCode.UNSUPPORTED_CLIENT) {
				message = "Please be sure that you have a valid username "
						+ "and password.";
			} else {
				message = "An unexpected error has occurred with "
						+ ex.getExceptionCode() + ".";
			}
			throw new SfdcException(ex, message);

		} catch (Exception ex) {
			throw new SfdcException(ex, "An unexpected error has occurred: "
					+ ex.getMessage());
		}

		if (loginResult.isPasswordExpired()) {
			throw new SfdcException("Your password has expired.");
		}

		/**
		 * Once the client application has logged in successfully, we use the
		 * results of the login call to reset the endpoint of the service to the
		 * virtual server instance that is servicing your organization. To do
		 * this, the client application sets the ENDPOINT_ADDRESS_PROPERTY of
		 * the binding object using the URL returned from the LoginResult. We
		 * use the metadata binding from this point forward as we are invoking
		 * calls in the metadata WSDL.
		 */

		try {
			metadatabinding = (MetadataBindingStub) new MetadataServiceLocator()
					.getMetadata();
		} catch (ServiceException e) {
			throw new SfdcException(e, "metadata binding failure!!");
		}
		metadatabinding._setProperty(
				MetadataBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult
						.getMetadataServerUrl());

		/**
		 * The sample client application now has an instance of the
		 * MetadataBindingStub that is pointing to the correct endpoint. Next,
		 * the sample client application sets a persistent SOAP header (to be
		 * included on all subsequent calls that are made with the
		 * SoapBindingStub) that contains the valid sessionId for our login
		 * credentials. To do this, the sample client application creates a new
		 * SessionHeader object and set its sessionId property to the sessionId
		 * property from the LoginResult object.
		 */
		// Create a new session header object and add the session id
		// from the login return object
		SessionHeader sh = new SessionHeader();
		sh.setSessionId(loginResult.getSessionId());
		/**
		 * Next, the sample client application calls the setHeader method of the
		 * SoapBindingStub to add the header to all subsequent method calls.
		 * This header will persist until the binding is destroyed or until the
		 * header is explicitly removed. The "SessionHeader" parameter is the
		 * name of the header to be added.
		 */
		// set the session header for subsequent call authentication
		metadatabinding.setHeader(new MetadataServiceLocator().getServiceName()
				.getNamespaceURI(), "SessionHeader", sh);
	}

	public void createMetadataObject(Metadata metaObj) {
		createMetadataObjects(new Metadata[] { metaObj });

	}

	public void createMetadataObjects(Metadata[] metaObjs) {
		List<Metadata> list = new ArrayList<Metadata>();

		for (int i = 0; i < metaObjs.length; i++) {
			if (list.size() == 10) {
				createMetadataTenObjects(list2Array(list));
				list.clear();
				continue;
			}
			list.add(metaObjs[i]);
		}

		if (list.isEmpty())
			return;

		createMetadataTenObjects(list2Array(list));
	}

	static private Metadata[] list2Array(List<Metadata> list) {
		return (Metadata[]) list.toArray(new Metadata[list.size()]);
	}

	public void createMetadataTenObjects(Metadata[] metaObjs) {
		try {
			AsyncResult[] ars = metadatabinding.create(metaObjs);

			if (ars == null) {
				throw new SfdcException(
						"AsyncResult is nuul. The object was not created successfully");
			}

			String createdObjectId = ars[0].getId();
			String[] ids = new String[] { createdObjectId };
			boolean done = false;
			long waitTimeMilliSecs = ONE_SECOND;
			AsyncResult[] arsStatus = null;

			/**
			 * After the create() call completes, we must poll the results of
			 * the checkStatus() call until it indicates that the create
			 * operation is completed.
			 */
			while (!done) {
				arsStatus = metadatabinding.checkStatus(ids);
				if (arsStatus == null) {
					System.out.println("The object status cannot be retrieved");
					return;
				}

				done = arsStatus[0].isDone();
				if (arsStatus[0].getStatusCode() != null) {
					System.out.println("Error status code: "
							+ arsStatus[0].getStatusCode());
					System.out.println("Error message: "
							+ arsStatus[0].getMessage());
				}
				Thread.sleep(waitTimeMilliSecs);
				// double the wait time for the next iteration
				waitTimeMilliSecs *= 2;
				System.out.println("The object state is "
						+ arsStatus[0].getState());
			}

			System.out.println("The ID for the created object is "
					+ arsStatus[0].getId());
		} catch (Exception ex) {
			throw new SfdcException(ex,
					"Failed to create object, error message was: \n"
							+ ex.getMessage());
		}

	}
}